package android.tusnotas.core;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.htmlcleaner.XPatherException;

import android.content.Context;
import android.tusnotas.beans.Course;
import android.tusnotas.beans.File;
import android.tusnotas.beans.Folder;
import android.tusnotas.beans.Grade;
import android.tusnotas.beans.Instance;
import android.tusnotas.beans.News;
import android.tusnotas.changes.GradeChange;
import android.tusnotas.changes.NewsChange;
import android.tusnotas.connector.webservice.WebServiceCaller;
import android.tusnotas.exception.ProcessingInstancesException;
import android.tusnotas.exception.RemoteReadingException;
import android.tusnotas.model.TusNotasModel;
import android.tusnotas.notification.NewGradeNotification;
import android.tusnotas.notification.NewsNotification;
import android.tusnotas.notification.TusNotasNotification;
import android.tusnotas.parser.TusNotasParser;
import android.tusnotas.parser.WebServiceParser;
import android.tusnotas.util.Devel;
import android.tusnotas.util.MD5Generator;
import android.util.Log;

public class GradeAnalyser {

	private static String XML_MD5 = "";
	private WebServiceCaller caller = new WebServiceCaller();
	
	private Context context = null;
	
	public Context getContext() {
		return context;
	}

	public void setContext(Context context) {
		this.context = context;
	}

	public String doGetRemoteGrades() throws RemoteReadingException{
		
		String XML="";
		
		try {
			caller.setContext(context);
			caller.setForGrades();
			XML = caller.execute();
		} catch (RemoteReadingException rwce) {
			//TODO CATCH IT AN THROW IT, JUST IN CASE WE NEED TO DO SOMETHING ABOUT IT
			throw rwce;
		} catch (Exception e){
			if( Devel.fullDevel){
				Log.w(this.getClass().getName(), "ERROR WHILE CONNECTING TO WC");
			}
			RemoteReadingException rwce = new RemoteReadingException();
			rwce.setErrorCode(GradeAnalyserStatuses.WEIRD_FAILURE);
			throw rwce;
		}

		return XML;
	}
	
	public List<Instance> doObtainInstances(String HTML) throws ProcessingInstancesException{
		
		List<Instance> instances=null;
		
		TusNotasParser tusNotasParser = new WebServiceParser();
		
		try {
			instances = tusNotasParser.parseXML(HTML);
			
		} catch (ProcessingInstancesException pie) {
			//CATCH IT AN THROW IT, JUST IN CASE WE NEED TO DO SOMETHING ABOUT IT
			throw pie;
		} catch (XPatherException xpe) {
			Log.e(this.getClass().getName(), "ERROR WHILE PARSING THE HTML");
			xpe.printStackTrace();
			throw new ProcessingInstancesException();
		}
		catch (Exception e) {
			Log.e(this.getClass().getName(), "ERROR WHILE PARSING THE HTML");
			e.printStackTrace();
			throw new ProcessingInstancesException();
		} 
		
		return instances;
	}
	
	public List<Instance> doGetGradesFromDB(Context context){
		TusNotasModel tnm = TusNotasModel.getTusNotasModel(context);
		
		List<Instance> instances = tnm.selectAllInstances();
		
		for (Instance instance : instances) {
			List<Course> courses = tnm.selectCoursesByInstance(instance.getId());
			for (Course course : courses) {
				course.setGrades(tnm.selectGradesByCourse(course.getId()));
			}
			instance.setCourses(courses);
		}
		return instances;
	}
	
	public Instance doGetInstanceAndGradesFromDB(Context context, Integer instanceId){
		TusNotasModel tnm = TusNotasModel.getTusNotasModel(context);
		
		Instance instance = tnm.selectInstanceById(instanceId);
		
		List<Course> courses = tnm.selectCoursesByInstance(instance.getId());
		for (Course course : courses) {
			course.setGrades(tnm.selectGradesByCourse(course.getId()));
		}
		instance.setCourses(courses);
		return instance;
	}

	public List<Instance> doGetGrades() throws RemoteReadingException, ProcessingInstancesException{
		
		String XML = doGetRemoteGrades();
		String XMLMd5 = MD5Generator.generateMD5(XML);
		
		if (XML_MD5.equals(XMLMd5)){
			//TODO TEST AND USE
			Log.w(this.getClass().getName(),"No changes in the XML (MD5) ");
			//return null;
		}
		XML_MD5 = XMLMd5;
		List<Instance> instances = doObtainInstances(XML);
		return instances;
	}
	
	public List<TusNotasNotification> doAnaliseGrades(List<Instance> instances, Context context){
		
		List<TusNotasNotification> result = new ArrayList<TusNotasNotification>();
		
		TusNotasModel tnm = TusNotasModel.getTusNotasModel(context);
		for (Instance instance : instances) {
			
			Instance instanceAux = tnm.selectInstanceByDescription(instance.getDescription());
			
			if (instanceAux == null){
				
				instanceAux = instance;
				instanceAux.setId(((Long) tnm.insertInstance(instance)).intValue());
				if (Devel.dbDevel || Devel.fullDevel){
					Log.w(this.getClass().getName(), "Need to Insert: "+instance.getDescription());
					Log.w(this.getClass().getName(), "Inserted: "+instanceAux.getId());
				}
			}
			else{
				if (Devel.dbDevel || Devel.fullDevel){
					Log.w(this.getClass().getName(), "Already There. Name: "+instanceAux.getDescription()+" Iid: "+instanceAux.getId());
				}
			}
			
			for (Course course : instance.getCourses()) {
				Course courseAux = tnm.selectCourseByInstanceAndName(instanceAux.getId(), course.getName());
				if (courseAux == null){
					courseAux = course;
					course.setInstanceId(instanceAux.getId());
					courseAux.setId(((Long) tnm.insertCourse(course)).intValue());
					if (Devel.dbDevel || Devel.fullDevel){
						Log.w(this.getClass().getName(), "Need to Insert. Instance: "+instance.getDescription()+" Course: "+course.getName());
						Log.w(this.getClass().getName(), "Inserted: "+courseAux.getId());
					}
				}
				else{
					if (Devel.dbDevel || Devel.fullDevel){
						Log.w(this.getClass().getName(), "Already There. Instance: "+instance.getDescription()+" Course: "+courseAux.getName() + " Cid: "+courseAux.getId());
					}
				}
				
				for (Grade grade : course.getGrades()) {
					Grade gradeAux = tnm.selectGradesByCourseAndActivity(courseAux.getId(), grade.getActivity());
					if (gradeAux == null){
						gradeAux = grade;
						grade.setCourseId(courseAux.getId());
						gradeAux.setId(((Long) tnm.insertGrade(grade)).intValue());
						if (Devel.dbDevel || Devel.fullDevel){
							Log.w(this.getClass().getName(), "Need to Insert. Course: "+course.getName()+" Grade: "+grade.getGrade());
							Log.w(this.getClass().getName(), "Inserted: "+gradeAux.getId());
						}
					}
					else if(grade.getGrade().equals(gradeAux.getGrade())){
						//NOTHING TO DO
						if (Devel.dbDevel || Devel.fullDevel){
							Log.w(this.getClass().getName(), "Already There. Course: "+course.getName()+" Grade:"+gradeAux.getGrade()+" Gid: "+gradeAux.getId());
						}
					}
					//THIS MEANS THAT THE GRADE WAS ALREADY THERE BUT IT HAS CHANGED
					else{
						//HERE WE NEED TO UPDATE THE GRADE AND NOTIFY THE USER.
						if ("-".equals(gradeAux.getGrade().trim())){
							if (Devel.dbDevel || Devel.fullDevel){
								Log.w(this.getClass().getName(), "\nNEW GRADE\n");
							}
						}
						else{
							if (Devel.dbDevel || Devel.fullDevel){
								Log.w(this.getClass().getName(), "\nUPDATED GRADE\n");
							}
						}
						TusNotasModel.getTusNotasModel(context);
						tnm.updateGrade(gradeAux.getId(), grade.getGrade());
						TusNotasNotification notification = new NewGradeNotification(new GradeChange(context, gradeAux, grade, courseAux));
						result.add(notification);
					}
				}

				// get news
				List<News> newsListWc = course.getNews();
				List<News> newsListDb = tnm.selectNewsByCourse(courseAux.getId());
				
				if (newsListDb.isEmpty()){ // no news in DB
					if (Devel.dbDevel || Devel.fullDevel){
						Log.w(this.getClass().getName(),"No hay noticias en la BD para el curso: "+courseAux.getName());
					}
					for(News newsWcAux : newsListWc){
						tnm.insertNews(newsWcAux.getContent(), courseAux.getId());
					}
				} else { 
					for (News newsWcAux : newsListWc){
						boolean inDb = false;
						for (News newsDbAux : newsListDb){
							if (newsWcAux.getContent().equals(newsDbAux.getContent())){ // both the same
								inDb = true;
								if (Devel.dbDevel || Devel.fullDevel){
									Log.w(this.getClass().getName(), "Already in Db: "+ newsWcAux.getContent());
								}
							}
						}
						if (!inDb){ // not in Db, add to Db, notify the user
							if (Devel.dbDevel || Devel.fullDevel){
								Log.w(this.getClass().getName(), "Not in DB (add and notify):"+newsWcAux.getContent());
							}
							tnm.insertNews(newsWcAux.getContent(), courseAux.getId());
							TusNotasModel.getTusNotasModel(context);
							TusNotasNotification notification = new NewsNotification(new NewsChange(context, newsWcAux, course));
							result.add(notification);
						}
					}
					for (News newsDbAux : newsListDb){
						boolean inWc = false;
						for (News newsWcAux : newsListWc){
							if (newsWcAux.getContent().equals(newsDbAux.getContent())){ // both the same
								inWc = true;
							}
						}
						if (!inWc){
							tnm.deleteNewsById(newsDbAux.getId());
							if (Devel.dbDevel || Devel.fullDevel){
								Log.w(this.getClass().getName(), "Removed from Db: "+ newsDbAux.getContent());
							}
						}
					}
				}
			}
		}
		return result;
	}
	
	private void folderAnalyzerHelper(Course course, TusNotasModel tnm){
		List <Folder> dbFolders = tnm.selectFoldersByCourse(course.getId());
		List <Folder> xmlFolders = course.getFolders();
		
		//set the reference to course
		for(Folder folder : xmlFolders){
			folder.setCourseId(course.getId());
		}
		
		List<Folder> folders = new ArrayList<Folder>();
		
		if( dbFolders.containsAll(xmlFolders) && xmlFolders.containsAll(dbFolders) ){
			//folders in the db are the same that folders in xml
			folders = dbFolders;
		}else if( !dbFolders.containsAll(xmlFolders) ){
			//there are folders to insert
			Set<Folder> foldersToInsert = absentElements(dbFolders, xmlFolders);
			for(Folder folder : foldersToInsert){
				Long id = tnm.insertFolder(folder,course.getId());
				folder.setId(id.intValue());
			}
			
			folders.addAll(dbFolders);
			folders.addAll(foldersToInsert);
		}else if( !xmlFolders.containsAll(dbFolders) ){
			
		}
		
		//now we go for files
		for(Folder folder : folders){
			fileAnalyzerHelper(folder, tnm);
		}
		
	}
	
	
	private void fileAnalyzerHelper(Folder folder, TusNotasModel tnm){
		List<File> dbFiles = tnm.selectAllFilesByFolder(folder.getId());
		List<File> xmlFiles = folder.getFiles();
		
		//set the reference to folder
		for(File file : xmlFiles){
			file.setFolderId(folder.getId());
		}
		
		if( dbFiles.containsAll(xmlFiles) && xmlFiles.containsAll(dbFiles) ){
			//files in the db are the same that files in xml
		}else if( !dbFiles.containsAll(xmlFiles) ){
			//there are files to insert
			Set<File> filesToInsert = absentElements(dbFiles, xmlFiles);
			for(File file : filesToInsert){
				tnm.insertFile(file, file.getFolderId());
			}
		}else if( !xmlFiles.containsAll(dbFiles) ){
			
		}
	}
	
	private static <T> Set<T> absentElements(List<T> l1, List<T> l2){
		Set <T> s1 = new HashSet<T>(l1);
		Set <T> s2 = new HashSet<T>(l2);
		
		// s2 - s1
		s2.removeAll(s1);
		return s2;
	}
	
}
